// hpp 后缀文件 , 头源不用分离写

#ifndef POCESS_POOL_HPP
#define POCESS_POOL_HPP

#include <iostream>
#include <vector>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "Task.hpp"

#define SUBPOCESSNUM 5

// 描述信道
class Channel
{
public:
    Channel(int wfd, int subid)
        : _wfd(wfd), _subprocessid(subid)
    {
        _name = "channel : " + std::to_string(_wfd) + "<->" + "subid : " + std::to_string(_subprocessid);
    }
    ~Channel()
    {
    }

    // 发送一个任务码 , 即 : 将任务码写入管道文件让子进程去读
    void Send(int taskcode)
    {
        write(_wfd, &taskcode, sizeof(taskcode));
    }

    void Close()
    {
        close(_wfd);
    }

    void Wait()
    {
        waitpid(_subprocessid, nullptr, 0);
    }
    std::string &Getname() { return _name; }

private:
    // 一个信道要包含什么 ? 匿名管道和子进程
    // 匿名管道要有其读写端 , 一个管道就代表有一个子进程 , 但是子进程是谁 ? 需要记录吗 ? 需要
    // 写端 , 子进程的 id , 再增加一个选择信息
    int _wfd; // 写端
    int _subprocessid;
    std::string _name; // 描述一个信道的基本信息
};

// 管理信道
class ChannelManage
{
public:
    ChannelManage()
    {
    }
    ~ChannelManage()
    {
    }

    // 插入信道管理接口 , 对象是 Channel , 插入写端和子进程 Id
    void Insert(int wfd, int subid)
    {
        _channels.emplace_back(wfd, subid); // 会走构造函数
    }

    // debug 接口
    void PrintManage()
    {
        // 遍历 _channel
        for (auto &channel : _channels)
        {
            std::cout << channel.Getname() << std::endl; // 打印一下
        }
    }

    // 选择一个信道 , 采用轮询方式选择 , 返回一个信道 , 准备通信得到任务
    Channel &Select()
    {
        auto &channel = _channels[_next]; // 得到下标 _next 的信道 , 初始从 0 开始
        _next++;
        _next %= _channels.size(); // 防止越界 , %以后回到下标为0处继续

        return channel;
    }

    //****************  关闭子进程继承下来的父进程原来所有的写端 , 关闭和等待同时在时的接口需要这个接口!
    void CloseAll()
    {
        for (auto &channel : _channels)
        {
            channel.Close();
        }
    }

    // 关闭父进程所有写端 , 遍历 _channels 即可
    void CloseWfd()
    {
        for (auto &channel : _channels)
        {
            channel.Close();
            std::cout << "关闭了 : " << channel.Getname() << std::endl;
        }
    }

    // 回收子进程
    void WaitSubProcess()
    {
        for (auto &channel : _channels)
        {
            channel.Wait();
            std::cout << "回收了 : " << channel.Getname() << std::endl;
        }
    }

    // 关闭和等待同时在
    void CloseAndWait()
    {
        for (auto &channel : _channels)
        {
            channel.Close();
            std::cout << "关闭了 : " << channel.Getname() << std::endl;
            
            channel.Wait();
            std::cout << "回收了 : " << channel.Getname() << std::endl;
        }
    }
private:
    // 多个信道怎么被管理 ? 这里用 vector 将其存储起来 , 里面是一个一个的信道
    std::vector<Channel> _channels;
    int _next = 0; // 表示下一个通道
};

// 进程池
class PocessPool
{
public:
    // 里面是具体进程池需要做的事情

    PocessPool()
    {
        // 注册任务
        _tasks.Register(UpLoad);
        _tasks.Register(DownLoad);
        _tasks.Register(Forward);
    }
    ~PocessPool()
    {
    }

    // 子进程读入任务码 , 完成任务
    void Work(int rfd)
    {
        while (true)
        {
            int code = 0;
            ssize_t n = read(rfd, &code, sizeof(code)); // 读到的就是父进程派发的任务码
            if (n > 0)
            {
                // 子进程读取要符合父进程给的任务码字节数 , 不符合继续读取 , 直到一样
                if (n != sizeof(code))
                {
                    continue;
                }
                // 拿到了任务码
                std::cout << "subprocess [" << getpid() << "] receive Task code :" << code << std::endl;
                // 执行任务码对应的任务
                _tasks.Execute(code);
            }
            else if (n == 0)
            {
                // 读取到管道结尾 , 子进程退出
                //std::cout << "subprocee quit !" << std::endl;
                break;
            }
            else
            {
                std::cout << "读取错误" << std::endl;
                break;
            }
        }
    }

    // 启动进程池 , 1 . 2 . 3 .步
    void Start()
    {
        // 创建多个子进程
        for (int i = 0; i < SUBPOCESSNUM; ++i)
        {
            // 1. 创建匿名管道文件
            int pipefd[2] = {0};
            int n = pipe(pipefd);
            if (n < 0)
            {
                perror("pipe");
                return;
            }

            // 2. 创建子进程
            pid_t id = fork();
            if (id < 0)
            {
                perror("fork");
                return;
            }
            else if (id == 0)
            {
                // child
                // 让子进程关闭继承下来的端 , 关闭哥哥进程的 w 端就行
                _chm.CloseAll();

                // 3 . 子进程关闭写端
                close(pipefd[1]);

                //.. 子进程完成不同工作
                Work(pipefd[0]);

                close(pipefd[0]);
                exit(0);
            }
            else
            {
                // father
                // 父进程关闭读端
                close(pipefd[0]);

                // .. 父进程完成创建管道和子进程后 , 插入到信道管理中
                _chm.Insert(pipefd[1], id);
            }
        }
    }

    // 运行进程池 , 就要派发任务
    void Run()
    {
        // 1 . 选择一个任务 , 即 : 选择一个任务码 , 这里让系统随机选择
        int taskcode = _tasks.Code();
        // 2 . 选择一个信道 , 即 : 任务码要派发给谁 ?
        auto &channel = _chm.Select();
        // 3 . 派发任务
        channel.Send(taskcode);
    }

    // 进程池的销毁 , 子进程退出父进程要等待
    void Stop()
    {
        // // 关闭父进程所有 wfd 端
        // _chm.CloseWfd();
        // // 回收所以子进程
        // _chm.WaitSubProcess();

        //用关闭和等待同时存在的接口 , 需要在创建子进程时加上 CloseAll 接口
        _chm.CloseAndWait();
    }

    void PrintPool()
    {
        _chm.PrintManage();
    }

private:
    // 一个进程池有多个信道 , 多个信道在哪 ? 被 ChannelManage 管理起来了
    ChannelManage _chm;  // 代表多个信道
    TaskMananger _tasks; // 任务管理列表
};
#endif
